# 剪辑近邻:去除分错的样本

from numpy import *
import random

# 存积极样本的词频向量
fileFrqPstv = '积极样本原始词频.txt'
# 存消极样本的词频向量
fileFrqNgtv = '消极样本原始词频.txt'
# 各自词频向量列表(实际声明不在此)
global pstvLst
global ngtvLst
# 总词频向量列表(实际声明不在此)
global dataLst
# 总词频向量列表的实时标签值(实际声明不在此)
global labelVec
# 考试集样本数(实际声明不在此)
global examNum
# 保存剪辑结果词频向量
fileClipFrq = '剪辑后的词频.txt'
# 保存剪辑结果词频对应的标签向量
fileClipLab = '剪辑后的标签.txt'


# 从文件读入样本集的词频向量列表
def readFrqLst():
	# 读入积极的词频向量列表
	global pstvLst
	pstvLst = []
	with open(fileFrqPstv, 'r', encoding='utf-8') as f:
		for line in f.readlines():
			# 本行转成的词频向量
			nowVec = [int(num) for num in line.split()]
			# 加入积极词频向量列表
			pstvLst.append(nowVec)
	# 读入消极的词频向量列表
	global ngtvLst
	ngtvLst = []
	with open(fileFrqNgtv, 'r', encoding='utf-8') as f:
		for line in f.readlines():
			# 本行转成的词频向量
			nowVec = [int(num) for num in line.split()]
			# 加入积极词频向量列表
			ngtvLst.append(nowVec)
	print("[从文件读入词频]积极样本%d个,消极样本%d个" % (len(pstvLst), len(ngtvLst)))


# 剪辑近邻:去除分错的样本
def clipNeighbor():
	# 初始化总词频向量列表
	global dataLst
	dataLst = pstvLst[:]  # 用切片复制一份积极样本集
	dataLst.extend(ngtvLst)  # 将消极样本集尾接

	# 初始化实时的标签值
	global labelVec
	labelVec = [1 for i in range(len(pstvLst))]
	labelVec.extend([0 for i in range(len(ngtvLst))])

	# 剪辑近邻,emptyNum记录连续正确分类的次数
	emptyNum = 0
	while (True):
		# 当前剩余样本集样本数
		lnth = len(dataLst)
		# 生成随机数表,用于打乱样本集
		rdmLst = [random.randint(0, lnth - 1) for i in range(lnth)]
		# 用随机数表打乱整个样本集
		for i in range(lnth):
			dataLst[i], dataLst[rdmLst[i]] = dataLst[rdmLst[i]], dataLst[i]
			labelVec[i], labelVec[rdmLst[i]] = labelVec[rdmLst[i]], labelVec[i]
		# 考试集样本数,取总剩余样本的1/4,随样本总数改变
		global examNum
		examNum = int(lnth / 4)
		# 考试并去除本次考试的错误样本
		Exam()
		# 判断examNum有没有减少即知是否有剪辑
		if int(lnth / 4) > examNum:
			emptyNum = 0
		else:
			emptyNum += 1
		if emptyNum >= 5:
			break
		break  # 这个break指示只剪辑一次
	# 剪辑结束后,保存剪辑结果
	# 特征矩阵
	with open(fileClipFrq, 'w+', encoding='utf-8') as f:
		f.truncate()  # 先清空要追加的文件
		# 对于剪辑剩下的每一条数据(词频向量)
		for frqVec in dataLst:
			# 其列表中的数字变字符串
			frqStrVec = [str(frq) for frq in frqVec]
			# 用空格插针,并尾接换行写入文件
			f.write(" ".join(frqStrVec) + "\n")
	# 标签向量
	with open(fileClipLab, 'w+', encoding='utf-8') as f:
		f.truncate()
		# 标签中1表示正样本,0表示负样本,数字列表变字符串列表
		labStrVec = [str(lab) for lab in labelVec]
		# 用空格插针,并写入文件
		f.write(" ".join(labStrVec))

	# 统计
	endPstv = 0
	endNgtv = 0
	###print(len(dataLst),len(labelVec))
	for i in range(len(labelVec)):
		if labelVec[i] == 1:
			endPstv += 1
		else:
			endNgtv += 1
	print('[剪辑近邻完成]剩余%d条正样本,%d条负样本' % (endPstv, endNgtv))


# 用后面的临时测试集对前面的考试集考试
def Exam():
	# 考试集列表的拷贝对象
	global examNum
	examLst = dataLst[0:examNum]
	# 临时测试集列表的拷贝对象
	tmpryTstLst = dataLst[examNum:]
	tmpryTstMat = mat(tmpryTstLst)  # 转换为mat对象
	# 临时测试集列表的标签的拷贝对象
	tmpryTstVec = labelVec[examNum:]
	###print(tmpryTstVec)

	# 临时测试集行数,即需要与考试者计算距离的样本数
	tmpryNum = tmpryTstMat.shape[0]
	# 对于每个待考试的考试集样本(下标)
	for i in range(examNum):
		# 重复成和临时测试集一样多的矩阵
		diffMat = tile(examLst[i], (tmpryNum, 1))
		# 和临时测试集相减得偏差矩阵
		diffMat = diffMat - tmpryTstMat

		# 使用L2范数:矩阵内每个元素平方
		sqDiffMat = multiply(diffMat, diffMat)

		# TODO 使用L1范数
		'''
		hang=diffMat.shape[0]
		lie=diffMat.shape[0]
		for i0 in range(hang):
			for i1 in range(lie):
				diffMat[i0,i1]=math.fabs(diffMat[i0,i1])
		sqDiffMat = diffMat
		'''

		# 行内求和,即得到和对应的临时测试集样本点的L2范数平方
		sqDstncMat = sqDiffMat.sum(axis=1)
		# mat变list
		sqDstncLst = [sqDstncMat[k, 0] for k in range(tmpryNum)]
		# list变array
		sqDstncArry = array(sqDstncLst)
		# 获取排序下标
		sortIdxArry = sqDstncArry.argsort()
		# 取前"考试集样本数/2*2+1"个最近的去判断(保证是奇数)
		votePstv = 0
		voteNgtv = 0
		for j in range(int(examNum / 2) * 2 + 1):
			# 第i近临时测试集实例的标签
			if tmpryTstVec[sortIdxArry[j]] == 1:
				votePstv += 1
			elif tmpryTstVec[sortIdxArry[j]] == 0:
				voteNgtv += 1
		# 考试结束,判断考试(预测)结果
		###print('[%d,%d]'%(votePstv,voteNgtv),end='')
		tstRslt = (1 if votePstv >= voteNgtv else 0)
		###print('%d,'%tstRslt,end='')
		# 和真实标签比较,错误时,标记应做剪辑近邻(暂不做)
		if tstRslt != labelVec[i]:
			print('%d,' % labelVec[i], end='')
			dataLst[i] = -1
			labelVec[i] = -1
	print()
	# 该轮考试结束,将标记者剪辑
	i = 0
	while (i < examNum):
		if dataLst[i] == -1:
			# 同步剪辑(一定是-1)
			dataLst.remove(-1)
			labelVec.remove(-1)
			i -= 1
			examNum -= 1
		i += 1
